
package com.studiofortress.sf.util.collections;

/**
 * This is essentially a 2D array where you can add elements anywhere in the
 * array (including negative values) and it will automatically resize and offset
 * it's values internally to adjust for this.
 * 
 * This means that the retrieval of an item takes at most two direct accesses to
 * standard arrays (one for the x-axis and another for the y-axis).
 *
 * Setting a value to null is the equivalent of removing it.
 *
 * @author Joseph Lenton - JosephLenton@StudioFortress.com
 * @param <V> The type of values stored in this collection.
 */
public class Resizable2DArray<V>
{
    private ResizableArray<ResizableArray<V>> xs;

    /**
     * Standard Constructor.
     */
    public Resizable2DArray()
    {
        this.xs = new ResizableArray<ResizableArray<V>>();
    }

    /**
     * Stores the given value at the given location. If the value is null then
     * it is considered as being removed.
     * @param x The x-location of the value to store.
     * @param y The y-location of the value to store.
     * @param val The value to store.
     */
    public void set(final int x, final int y, final V val)
    {
        ResizableArray<V> ys = xs.get(x);

        if ( ys == null ) {
            ys = new ResizableArray<V>();
            xs.set(x, ys);
        }

        ys.set(y, val);
    }

    /**
     * If the location is out of bounds then null is returned (because there is
     * nothing stored outside of the bounds).
     * @param x The x-location to get the value from.
     * @param y The y-location to get the value from.
     * @return Null if there is no value stored at this location, otherwise the value stored there.
     */
    public V get(final int x, final int y)
    {
        final ResizableArray<V> ys = xs.get(x);

        if ( ys != null ) {
            return ys.get(y);
        }
        
        return null;
    }

    /**
     * Sets the value stored at x,y to null and returns the old value there.
     * The old value is null if there was no value.
     * @param x The x location of the value to remove.
     * @param y The y location of the value to remove.
     * @return Null if there is no value stored, otherwise the value being removed.
     */
    public V remove(final int x, final int y)
    {
        final ResizableArray<V> ys = xs.get(x);

        if ( ys != null ) {
            final V temp = ys.get(y);
            ys.set(y, null);
            return temp;
        }

        return null;
    }

    /**
     * @param x The x location of the value to check for.
     * @param y The y location of the value to check for.
     * @return True if there is a non-null value stored at x,y.
     */
    public boolean isSet(final int x, final int y)
    {
        return get(x, y) != null;
    }

    /**
     * Removes all values from the array.
     */
    public void clear()
    {
        xs = new ResizableArray<ResizableArray<V>>();
    }
    
    /**
     * Cleares a whole strip of values along the horizontal axis.
     * @param x The row to clear.
     */
    public void clear(int x)
    {
        xs.set(x, null);
    }
}
